#
# PlanetfallUnitTestingFramework
#
# This file is used to help run any Unit Tests that can be done.
#
# Since using the default unittest module in python will assume that you're 
# running from the command line, the runner will use sys.exit(),
# effectively crashing Civ4. This class is used instead of the unittest
# console program so that unit testing can be done within Civ4.
#


import unittest
import time
import traceback

def getTests():
    # Add modules to this list to have them included in the tests.
    TEST_MODULES_LIST = [
        "PlanetValuesTests"
    ]
    return TEST_MODULES_LIST


class PlanetfallUnitTester:
    """
    The new Tester class, now sys.exit free!
    """
    def __init__(self, defaultTest=None,
                 testRunner=None,
                 testLoader=unittest.TestLoader()):
        self.verbosity = 1
        self.defaultTest = defaultTest
        self.testRunner = testRunner
        self.testLoader = testLoader
        if testRunner is None:
            self.testRunner = unittest.TextTestRunner(verbosity=self.verbosity)

    def runTests(self, szTestNames):
        self.tests = self.testLoader.loadTestsFromNames(szTestNames)
        
        result = self.testRunner.run(self.tests)
        return result

class SimpleTestResults:
    """
    A new TestResults class that can be used for outputting text into a simple
    string, so that the tests can be run somewhere outside of the built-in
    python shell.
    """

    def __init__(self):
        self.failures = []
        self.errors = []
        self.testsRun = 0
        self.shouldStop = 0
        self.resultString = ""

    def startTest(self, test):
        "Called when the given test is about to be run"
        self.testsRun = self.testsRun + 1

    def stopTest(self, test):
        "Called when the given test has been run"
        pass

    def addError(self, test, err):
        """Called when an error has occurred. 'err' is a tuple of values as
        returned by sys.exc_info().
        """
        self.errors.append((test, self._exc_info_to_string(err, test)))
        self.resultString += "E"

    def addFailure(self, test, err):
        """Called when an error has occurred. 'err' is a tuple of values as
        returned by sys.exc_info()."""
        self.failures.append((test, self._exc_info_to_string(err, test)))
        self.resultString += "F"

    def addSuccess(self, test):
        "Called when a test has completed successfully"
        self.resultString += "."

    def wasSuccessful(self):
        "Tells whether or not this result was a success"
        return len(self.failures) == len(self.errors) == 0

    def stop(self):
        "Indicates that the tests should be aborted"
        self.shouldStop = True

    def _exc_info_to_string(self, err, test):
        """Converts a sys.exc_info()-style tuple of values into a string."""
        exctype, value, tb = err
        # Skip test runner traceback levels
        while tb and self._is_relevant_tb_level(tb):
            tb = tb.tb_next
        if exctype is test.failureException:
            # Skip assert*() traceback levels
            length = self._count_relevant_tb_levels(tb)
            return ''.join(traceback.format_exception(exctype, value, tb, length))
        return ''.join(traceback.format_exception(exctype, value, tb))

    def _is_relevant_tb_level(self, tb):
        return tb.tb_frame.f_globals.has_key('__unittest')

    def _count_relevant_tb_levels(self, tb):
        length = 0
        while tb and not self._is_relevant_tb_level(tb):
            length += 1
            tb = tb.tb_next
        return length

    def __repr__(self):
        return "<%s run=%i errors=%i failures=%i>" % \
               (_strclass(self.__class__), self.testsRun, len(self.errors),
                len(self.failures))

class SimpleTextTestRunner:
    """
    A new TestRunner class that can be used for outputting text into a simple
    string, so that the tests can be run somewhere outside of the built-in
    python shell.
    """
    def __init__(self, descriptions=1, verbosity=1):
        self.descriptions = descriptions
        self.verbosity = verbosity

    def run(self, test):
        "Run the given test case or test suite."
        startTime = time.time()
        result = SimpleTestResults()
        test(result)
        stopTime = time.time()
        timeTaken = stopTime - startTime

        resultString = result.resultString
        resultString += "\n\n\n"
        resultString += "Ran %d tests in %ds\n\n" % (result.testsRun, timeTaken)

        if result.wasSuccessful():
            resultString += "All tests passed sucessfully\n"
        else:
            for error in result.errors:
                resultString += "--------------------------------------------\n"
                resultString += "                   ERROR                    \n"
                resultString += "--------------------------------------------\n"
                resultString += "%s\n%s\n\n" % (str(error[0]), str(error[1]))

            for failure in result.failures:
                resultString += "--------------------------------------------\n"
                resultString += "                  FAILURE                   \n"
                resultString += "--------------------------------------------\n"
                resultString += "%s\n%s\n\n" % (str(failure[0]), str(failure[1]))

        
            
        return resultString
        

def runTests():
    pTestingProg = PlanetfallUnitTester()
    pTestingProg.runTests(getTests())

def runTestsInOptionsScreen():
    pTestingProg = PlanetfallUnitTester(testRunner=SimpleTextTestRunner())
    return pTestingProg.runTests(getTests())
